package com.gitee.feizns.dynamic.time;


import com.gitee.feizns.dynamic.DateUtils;

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Year;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.ValueRange;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;

/**
 * 日期计算工具类
 * <p>
 *     对常用的日期格式、日期计算进行的封装
 * </p>
 * @author feizns
 * @since 2022/09/07
 */
public abstract class Times {

    /**
     * 日期时间
     * @see DateTimeFormatter#ISO_LOCAL_DATE_TIME
     */
    public static final DateTimeFormatter LOCAL_DATE_TIME = new DateTimeFormatterBuilder()
                .parseCaseInsensitive()
                .append(DateTimeFormatter.ISO_LOCAL_DATE)
                .appendLiteral(' ')
                .append(DateTimeFormatter.ISO_LOCAL_TIME)
                .toFormatter();

    /**
     * 日期时间
     * @see DateTimeFormatter#ISO_DATE_TIME
     */
    public static final DateTimeFormatter DATE_TIME = new DateTimeFormatterBuilder()
                .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
                .optionalStart()
                .appendOffsetId()
                .optionalStart()
                .appendLiteral('[')
                .parseCaseSensitive()
                .appendZoneRegionId()
                .appendLiteral(']')
                .toFormatter();

    /**
     * 日期时间解析器
     */
    private static final List<Function<String, LocalDateTime>> parers = new ArrayList<>();

    static {
        parers.add(LocalDateTime::parse);
        parers.add(val -> LocalDateTime.parse(val, DateTimeFormatter.ISO_LOCAL_DATE_TIME));
        parers.add(val -> LocalDateTime.parse(val, DateTimeFormatter.ISO_DATE_TIME));
        parers.add(val -> LocalDateTime.parse(val, LOCAL_DATE_TIME));
        parers.add(val -> LocalDateTime.parse(val, DATE_TIME));
        parers.add(val -> LocalTime.parse(val, DateTimeFormatter.ISO_LOCAL_TIME).atDate(LocalDate.ofEpochDay(0)));
        parers.add(val -> LocalDate.parse(val).atStartOfDay());
        parers.add(val -> YearMonth.parse(val).atDay(1).atStartOfDay());
        parers.add(val -> Year.parse(val).atDay(1).atStartOfDay());
    }

    /**
     * 注册解析器
     * @param parser 解析 器
     */
    public static void register(Function<String, LocalDateTime> parser) {
        parers.add(parser);
    }

    /**
     * 解析
     * @param datetime 日期时间
     * @return {@link LocalDateTime }
     */
    public static LocalDateTime parse(String datetime) {
        for (Function<String, LocalDateTime> parser : parers) {
            try {
                return parser.apply(datetime);
            } catch (Exception e) {
                // ignore
            }
        }
        return null;
    }

    /**
     * 解析
     * @param datetime 日期时间
     * @param targetType 目标类型
     * @return {@link T }
     */
    public static <T extends TemporalAccessor> T parse(String datetime, Class<T> targetType) {
        LocalDateTime time = parse(datetime);
        return time != null ? DateUtils.to(time, targetType) : null;
    }

    /**
     * 在对应周的第一天
     * @param date 日期
     * @return {@link LocalDate}
     */
    public static LocalDate atStartOfWeek(LocalDate date) {
        return date.with(DayOfWeek.MONDAY);
    }

    /**
     * 本周一日期
     * @return {@link LocalDate}
     */
    public static LocalDate atStartOfWeek() {
        return atStartOfWeek(LocalDate.now());
    }

    /**
     * 昨天
     * @return {@link LocalDate }
     */
    public static LocalDate yesterday() {
        return yesterday(LocalDate.now());
    }

    /**
     * 昨天
     * @param date {@link LocalDate}
     * @return {@link LocalDate }
     */
    public static LocalDate yesterday(LocalDate date) {
        return date.minusDays(1);
    }

    /**
     * 明天
     * @return {@link LocalDate }
     */
    public static LocalDate tomorrow() {
        return tomorrow(LocalDate.now());
    }

    /**
     * 明天
     * @param date 日期
     * @return {@link LocalDate }
     */
    public static LocalDate tomorrow(LocalDate date) {
        return date.plusDays(1);
    }

    /**
     * 获取开始年份和结束年份之间的年份
     * @param start 开始
     * @param end 结束
     * @return {@link List}<{@link Year}>
     */
    public static List<Year> years(Year start, Year end) {
        List<Year> years = new ArrayList<>();
        while ( end.isAfter(start) ) {
            years.add(start);
            start = start.plusYears(1);
        }
        return years;
    }

    /**
     * 获取开始日期和结束日期之前的所有的月份
     * @param startMonth 开始日期
     * @param endMonth 结束日期
     * @return {@link YearMonth}
     */
    public static List<YearMonth> months(YearMonth startMonth, YearMonth endMonth) {
        List<YearMonth> months = new ArrayList<>();
        while ( endMonth.isAfter(startMonth) ) {
            months.add(startMonth);
            startMonth = startMonth.plusMonths(1);
        }
        return months;
    }

    /**
     * 获取对应年份的所有月份
     * @param year 年份
     * @return {@link YearMonth}
     */
    public static List<YearMonth> months(Year year) {
        ValueRange range = ChronoField.MONTH_OF_YEAR.range();
        List<YearMonth> months = new ArrayList<>();
        for (long i = range.getMinimum(); i <= range.getMaximum(); i++)
            months.add(year.atMonth((int) i));
        return months;
    }

    /**
     * 获取开始日期和结束日期之前的所有日期
     * @param startDate 开始日期
     * @param endDate 结束日期
     * @return {@link LocalDate}
     */
    public static List<LocalDate> dates(LocalDate startDate, LocalDate endDate) {
        List<LocalDate> dates = new ArrayList<>();
        while ( endDate.isAfter(startDate) ) {
            dates.add(startDate);
            startDate = startDate.plusDays(1);
        }
        return dates;
    }

}
